package jpa4azure.type;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PersistenceException;
import javax.persistence.Table;

import jpa4azure.impl.Persistable;


class DefaultTypeWrapper extends TypeWrapper {
	protected Map<String,Field> fields;
	protected Field idfield;
	protected boolean isGenerated;
	protected Class<?> me;
	protected String tableName;
	protected boolean hasKey = false;
	protected Jpa4AzureAnnotations helper;
	protected Collection<Field> aggregateFields;

	public DefaultTypeWrapper(Class<?> c, Jpa4AzureAnnotations helper) {
		me = c;
		
		// find the annotated class in hierarchy
		while (c!= null && ! c.isAnnotationPresent(Entity.class))
			c = c.getSuperclass();
		if (c==null) {
			throw new PersistenceException(me + " or a superclass must be annotated as a JPA @Entity");
		}
		if (c.isAnnotationPresent(Table.class))
			tableName = c.getAnnotation(Table.class).name();
		else
			tableName = c.getSimpleName();

		this.helper = helper;
		fields = getDeclaredFields(me);
		aggregateFields = new ArrayList<Field>();
		for (Field f : fields.values()) {
			if (Modifier.isTransient(f.getModifiers()))
				continue;
			if (Key.class.isAssignableFrom(f.getType())) {
				idfield = f;
				hasKey = true;
			} else if (f.isAnnotationPresent(Id.class)) {
				idfield = f;				
				if (f.isAnnotationPresent(GeneratedValue.class))
					isGenerated = true;
			} else if (f.getType() == Collection.class) {
				Class<?> generic = Utils.genericType(f);
				if (Utils.isEntity(generic))
					aggregateFields.add(f);
			}
			f.setAccessible(true);
		}
	}
	
	public Field getField(String name) {
		return fields.get(name);
	}
	
	public Key id(Object o) {
		try {
			//if (!idfield.isAccessible())
			//	idfield.setAccessible(true);
			if (isGenerated && idfield.get(o)==null)
				idfield.set(o, UUID.randomUUID());
			if (hasKey)
				return new TypedKey( (Key)idfield.get(o), this.me );
			else {
				return new TypedKey(idfield.get(o).toString(), this.me);
			}
		} catch (Exception e) {
			logger.log(Level.WARNING, "Error retrieving id field value.", e);
		}
		return null;
	}

	public FieldContext[] getFields(Object o) {
		return helper.getFields(fields.values(), o);
	}
	
	public Collection<Field> getFields() {
		return fields.values();
	}
	
	public Collection<Field> getAggregateFields() {
		return aggregateFields;
	}

	public FieldContext[] getDirtyFields(Persistable o) {
		List<Field> tmp = new ArrayList<Field>();
		for(String name : o.dirty())
			tmp.add(fields.get(name));
		return helper.getFields(tmp, o);
	}

	@Override
	public Object newInstance() {
		try {
			return me.newInstance();
		} catch (Exception e) {
			Utils.runtime("Your Java Object must provide jo4neo with a default constructor.", e);
		}
		return null;
	}

	@Override
	public void setId(Object bean, String partition, String row) {		
		try {
			//if (!idfield.isAccessible())
			//	idfield.setAccessible(true);	
			Constructor<?> constructor = idfield.getType().getConstructor(String.class, String.class);
			Object o = constructor.newInstance(partition, row);
			idfield.set(bean, o);
		} catch (Exception e) {
			Utils.runtime(e);
		}
	}

	@Override
	public Class<?> getWrappedType() {
		return this.me;
	}
	
	public String name() {
		return me.getSimpleName();
	}
	
	private Map<String, Field> getDeclaredFields(Class c) {
		Map<String, Field> fields = new HashMap<String, Field>();
		for (Field field : c.getDeclaredFields())
			fields.put(field.getName(), field);
		Class<?> cls = c; 
		while (cls.getSuperclass() != Object.class && cls.getSuperclass() != null) {
			cls = cls.getSuperclass();
			for (Field field : cls.getDeclaredFields())
				fields.put(field.getName(),field);
		}
		return fields;
	}
	

	@Override
	public String getTableName() {
		return tableName;
	}
	
	public boolean isHasKey() {
		return hasKey;
	}

}